#ifndef ABB523B9_AE48_44CC_A12E_EFC41A4F2371
#define ABB523B9_AE48_44CC_A12E_EFC41A4F2371
/*日志落地模块的实现
    1、抽象落地基类
    2、派生子类（根据不同落地方向， 进行派生）
    3、使用工厂模式进行创建与表示分离
*/
#include <iostream>
#include <fstream>
#include "util.hpp"
#include <memory>
#include <cassert>
#include "format.hpp"
#include <sstream>

namespace mian_yang
{
    class LogSink
    {
    public:
        using ptr = std::shared_ptr<LogSink>;

        virtual ~LogSink() {} // 让用户进行自行创建落地方向, 所以就需要进行虚函数化析构
        virtual void log(const char *data, size_t len) {}
    };

    // 落地方向:1、标准输出， 2、指定文件. 3、滚动文件（以大小进行滚动）
    class StdoutSink : public LogSink
    {
    public:
        // 将日志详细写入到标准输出
        void log(const char *data, size_t len)
        {
            std::cout.write(data, len);
        }
    };
    class FileSink : public LogSink
    {
    public:
        // 构造落地标准时， 直接打开文件
        FileSink(std::string filename) : _filename(filename)
        {
            // 1、创建日志文件所在的目录
            util::File::createDirectory(util::File::path(filename));
            // 2、创建并打开这个文件
            _ofs.open(_filename, std::ios::binary | std::ios::app);
            // 异常处理
            assert(_ofs.is_open());
        }
        // 将日志详细写入到指定文件
        void log(const char *data, size_t len)
        {
            _ofs.write(data, len);
        }

    private:
        std::string _filename;
        std::ofstream _ofs; // 文件句柄
    };

    class RollSink : public LogSink
    {
    public:
        RollSink(const std::string basename, size_t max_size)
            : _basename(basename),
              _max_fileSize(max_size),
              _cur_fileSize(0),
              name_count(0)
        {
            std::string filename = createNewFile(); // 获得目录 + 文件名
            // 1、创建日志文件所在的目录
            util::File::createDirectory(util::File::path(filename));
            // 2、创建并打开这个文件
            _ofs.open(filename, std::ios::binary | std::ios::app);
            // 异常处理
            assert(_ofs.is_open());
        }

        // 将日志消息写入到滚动文件-写入前判断文件大小， 超过最大大小就要切换文件。这个过程要获取文件的属性。 效率低， 就维护两个文件大小
        void log(const char *data, size_t len)
        {
            if (_cur_fileSize >= _max_fileSize)
            {
                std::string filename = createNewFile();
                _ofs.close(); // 关闭旧文件

                // 1、创建日志文件所在的目录
                util::File::createDirectory(util::File::path(filename));
                // 2、创建并打开这个文件
                _ofs.open(filename, std::ios::binary | std::ios::app);
                // 异常处理
                assert(_ofs.is_open());
                _cur_fileSize = 0;
            }
            _ofs.write(data, len);
            _cur_fileSize += len;
        }

    private:
        std::string createNewFile() // 进行大小判断， 超过指定大小则创建新文件。
        {
            // 获取系统时间， 以时间来构造文件名扩展名
            time_t cur_t = util::Date::getTime();
            struct tm l_cur_t;
            localtime_r(&cur_t, &l_cur_t);

            std::string filename = _basename;
            std::stringstream ssfs;
            ssfs << filename;
            ssfs << l_cur_t.tm_year + 1900;
            ssfs << l_cur_t.tm_mon + 1;
            ssfs << l_cur_t.tm_mday;
            ssfs << l_cur_t.tm_hour;
            ssfs << l_cur_t.tm_min;
            ssfs << l_cur_t.tm_sec;
            ssfs << "-" << name_count++;
            ssfs << ".log";
            return ssfs.str();
        }

    private:
        // 通过基础文件名 + 扩展文件名（以时间生成） 组成一个实际的当前的输出文件名
        std::string _basename; //./logs/base-             -> ./logs/base-202502221629
        std::ofstream _ofs;    // 文件句柄
        size_t _cur_fileSize;  // 当前文件已经写入到文件大小
        size_t _max_fileSize;  // 记录文件大小， 当前文件超过了这个大小， 就要切换文件
        int name_count;
    };

    /*扩展出一个以时间作为日志文件滚动切换类型的日志落地模块
        1、以时间进行文件滚动， 实际上是以时间段进行滚动
        2、实现思想:time（nullptr)& gap（时间间隙）;
        time(nullptr) % 60  得到的结果假如为n， 那么
        当前就是第n个60秒。得到的这个n与当前文件的时间段是否一直，
        不一致代表不是同一个时间段。*/

    enum class TimeGap
    {
        GAP_SECOND,
        GAP_MINUTE,
        GAP_HOUR,
        GAP_DAY,
    };

    class RollByTimeSink : public LogSink
    {
    public:
        RollByTimeSink(const std::string basename, TimeGap gap)
            : _basename(basename)
        {
            switch (gap)
            {
            case TimeGap::GAP_SECOND:
                _gap_size = 1;
                break;
            case TimeGap::GAP_MINUTE:
                _gap_size = 60;
                break;
            case TimeGap::GAP_HOUR:
                _gap_size = 3600;
                break;
            case TimeGap::GAP_DAY:
                _gap_size = 3600 * 24;
                break;
            }
            //获取当前是第几个时间段
            _cur_gap = _gap_size == 1 ? util::Date::getTime() : util::Date::getTime() % _gap_size;

            std::string filename = createNewFile();
            // 1、创建日志文件所在的目录
            util::File::createDirectory(util::File::path(filename));
            // 2、创建并打开这个文件
            _ofs.open(filename, std::ios::binary | std::ios::app);
            // 异常处理
            assert(_ofs.is_open());
        }

        // 将日志消息写入到滚动文件， 判断当前时间是否是当前文件的时间段， 不是则切换文件。
        void log(const char *data, size_t len)
        {
            time_t cur = util::Date::getTime();
            if (cur % _gap_size != _cur_gap)
            {
                _ofs.close();
                std::string filename = createNewFile();
                // 1、创建日志文件所在的目录
                util::File::createDirectory(util::File::path(filename));
                // 2、创建并打开这个文件
                _ofs.open(filename, std::ios::binary | std::ios::app);
                // 异常处理
                assert(_ofs.is_open());
                _cur_gap = _gap_size == 1 ? cur : cur % _gap_size;
            }
            _ofs.write(data, len);
            _ofs.good();
        }

    private:
        std::string createNewFile() // 进行大小判断， 超过指定大小则创建新文件。
        {
            // 获取系统时间， 以时间来构造文件名扩展名
            time_t cur_t = util::Date::getTime();
            struct tm l_cur_t;
            localtime_r(&cur_t, &l_cur_t);

            std::string filename = _basename;
            std::stringstream ssfs;
            ssfs << filename;
            ssfs << l_cur_t.tm_year + 1900;
            ssfs << l_cur_t.tm_mon + 1;
            ssfs << l_cur_t.tm_mday;
            ssfs << l_cur_t.tm_hour;
            ssfs << l_cur_t.tm_min;
            ssfs << l_cur_t.tm_sec;
            ssfs << ".log";
            return ssfs.str();
        }

    private:
        // 通过基础文件名 + 扩展文件名（以时间生成） 组成一个实际的当前的输出文件名
        std::string _basename; //./logs/base-             -> ./logs/base-202502221629
        std::ofstream _ofs;    // 文件句柄

        size_t _cur_gap;  // 当前时间段是第几个
        size_t _gap_size; // 时间段的大小
    };

    // 简单工厂模式
    template <class SinkType>
    class SinkFactory
    {
    public:
        template <class... Args>
        static LogSink::ptr create(Args &&...args)
        {
            /*参数是可变参数， 根据可变参数初始化智能指针。得到一个智能指针里面有落地log接口*/
            return std::make_shared<SinkType>(std::forward<Args>(args)...);   
        }
    };


}

#endif /* ABB523B9_AE48_44CC_A12E_EFC41A4F2371 */
